<?php
/**
* DataTable.php
*
* Contains DataTable class definition
* @author  John Snook <jsnook@gmail.com>
* @version 2
* @copyright (c) Snook 2004
* @example /var/www/corp/IntraDox/Documents.php
* @package QuickDataFiles
*/
/**
* DataTable
*
* This class allows a user to generate an HTML datatable.  It is the container object for all other classes
*<b>Example Usage</b>
*<code><?php
* </code>
* @author  John Snook <jsnook@gmail.com>
* @version 2
* @copyright (c) Snook 2004
* @example /var/www/corp/IntraDox/Documents.php
* @todo Break container out from DataTable
* @package QuickData
*/
class DataTable {
	/**
	* @access public
	* @var string 
	*/
	var $ID = NULL; // *new* unique identifer for this table
	var $Title = NULL;
	var $DBTableName = NULL;
	var $ConnectionString;	
	var $PrimaryKeyName;
	var $SortIconsURL = "http://corp.newfields.com/QuickerData/images/sort/";
	/**
	* JavaScript Object name for generating click events.
	* @access public
	* @var string 
	*/
	var $ObjectName;
	/**
	* Location of quickdata library, ie /IntraDox/QuickData.
	* @access public
	* @var string 
	*/
	var $QuickDataPath = NULL;
	/**
	* Array of DataTableColumn objects
	* @access public
	* @var DataTableColumn[]
	*/
	var $Columns;

	/**
	* lets turn this into an object at some point
	* @access private
	* @var string
	*/
	var $_Query = "";
	/**
	* New! array of CriteriaField objects!
	* @access public
	* @var array
	*/
	var $Criteria;
	
	/**
	* DataTablePagingControl object
	* @access public
	* @var DataTablePagingControl
	*/
	var $PageSize = NULL;
	var $CurrentPage = NULL;

	/**
	* Search control for search form
	* @access public
	* @var QuickDataSearch
	*/
	var $SearchControl = NULL;
	var $Requires = "";

	/**
	* ShowStatusBar property toggles whether the form is shown with the record count status bar or without.
	* @access public
	* @var boolean
	*/
	var $ShowStatusBar = FALSE;

	/**#@-*/
	
	var $SortStrings = array(QDT_SORT_NONE => "NONE", QDT_SORT_ASC => "ASC", QDT_SORT_DESC => "DESC");
	
	/**
	* Class Constructor
	* @param string $XMLFile the name and path of the xml file that provides settings for this grid.
	* @param string $TableID the name of the DataTable node in the xml file...
	*/
	function DataTable($XMLFile, $TableID, $ObjectName)
	{
		$this->ID = $TableID;
		$this->ObjectName = $ObjectName;

		// Open the XML definition
		$xml = simplexml_load_file($XMLFile);
		// Select our DataTable definition
		$Definition = $xml->xpath("//DataTable[@ID=\"$this->ID\"]");
		$Definition = $Definition[0];	
		// DataTable attributes
		foreach($Definition->attributes() as $key => $value)
		{
			$php = '$this->' . "$key = '" . addslashes($value) . "';";
			eval($php);
		}
		if(!is_null($this->PageSize)) $this->PageSize = (int) $this->PageSize;
		if(strtolower($Definition['Searchable'])  == "true" ? true : false)
		{
			$this->CreateSearch("Search", QS_SIMPLE);
		}
		
		// DataTable Columns
		foreach($Definition->xpath('//Column') as $ColumnDef)
		{
			$Visible = strtolower($ColumnDef['Visible'])  == "true" ? true : false;
			$Sortable = strtolower($ColumnDef['Sortable'])  == "true" ? true : false;
			$Searchable = strtolower($ColumnDef['Searchable'])  == "true" ? true : false;
			$NewColumn = $this->AddColumn((string)$ColumnDef['DataMember'], (string)$ColumnDef['Caption'], 
				$Sortable, $Searchable);
			$NewColumn->Visible = $Visible;
			if($ColumnDef['TemplateRenderFunc'])
			{
				$NewColumn->TemplateRenderFunc = (string)$ColumnDef['TemplateRenderFunc'];
			}
		}

		$DB = new DBConnection($this->ConnectionString);
		if ($DB == 0) die( "$DB is not an open connection.");
		if (stristr($this->_Query, "order by")) die( "DataTable::Query can not contain ORDER BY statements.");
		if (stristr($this->_Query, "limit")) die( "DataTable::Query can not contain LIMIT statements.");
	} //DataTable
	
	/**
	* Handles special requests that only our control would make.
	* @todo add QDT_ in front of all request variable names.
	* @param Array $Request $_REQUEST array 
	*/
	function HandleEvents($Request)
	{
		$eventoccured = FALSE;

		if (isset($Request["Page"]) && !is_null($this->PageSize))
		{
			$this->CurrentPage = (int)$Request["Page"];
			//$this->DataForm->DisplayMode = QD_MODE_NONE;
		}
	
		if (isset($Request["PageSize"])){
			$this->PageSize = (int)$Request["PageSize"];
			$eventoccured == TRUE;
		}
	
		// Send search events
		if (!is_null($this->SearchControl) && $eventoccured == FALSE)
		{
			$eventoccured = $this->SearchControl->HandleEvents($Request);
			if($eventoccured && !is_null($this->PageSize))
			{
				$this->CurrentPage = 1;
			}
		}

		if (isset($Request["Sort"])){
			if(isset($Request["SortDirection"]) && $Request["SortDirection"] != "")
			{
				$this->Columns[$Request["Sort"]]->SortDirection = (int)$Request["SortDirection"];
			}
			else
			{
				$currentDirection = $this->Columns[$Request["Sort"]]->SortDirection;
				$this->Columns[$Request["Sort"]]->SortDirection = ($currentDirection ==  QDT_SORT_DESC) ? QDT_SORT_NONE : $currentDirection + 1;
			}
			$eventoccured == TRUE;
		}

		if (isset($Request["AddCriteria"])){
			$Criteria = $Request['Criteria'];
			foreach($Criteria as $CriteriaField)
			{
				$Properties = split(',', $CriteriaField);
				$this->Criteria[] = new CriteriaField(stripslashes($Properties[0]), stripslashes($Properties[1]), $Properties[2]);
			}
			$eventoccured == TRUE;
		}

		if (isset($Request["SetCriteria"])){
			$this->Criteria[] = NULL;
			$Criteria = $Request['Criteria'];
			foreach($Criteria as $CriteriaField)
			{
				$Properties = split(',', $CriteriaField);
				$this->Criteria[] = new CriteriaField($Properties[0], $Properties[1], $Properties[2]);
			}
			$eventoccured == TRUE;
		}
		return $eventoccured;
	}
	
	function AddColumn($DataMember, $Name, $Sortable = FALSE, $Searchable = FALSE, $Visible = TRUE)
	{
		$this->Columns[$DataMember] = new DataTableColumn($DataMember, $Name, $Sortable, $Searchable, $Url);
		$this->Columns[$DataMember]->Visible = $Visible;
		$this->Columns[$DataMember]->Parent = &$this;
		return $this->Columns[$DataMember];
	} //AddColumn
	
	function CreateSearch($Title = "", $DisplayMode = QS_SIMPLE)
	{
		$this->SearchControl = new Search($this->Columns, $DisplayMode, $this->ObjectName, $Title);
	} //CreateSearch	

	function RecordCount($IgnorePaging = TRUE)
	{
		// Get a quick record count
//		echo $this->GetQuery(TRUE);
		$DB = new DBConnection($this->ConnectionString);

		$Query = $DB->CreateQuery($this->GetQuery($IgnorePaging));
		$count = $Query->NumRows();
		return $count;
	}
	
	function GetQuery($IgnorePaging = FALSE)
	{
		$Query = $this->_Query . " ";

		// New! array of CriteriaField objects!
		if (is_array($this->Criteria))
		{
			$search = (stristr($Query, "where") ? " AND ( " : " WHERE (");
			foreach($this->Criteria as $Crit)
			{
				$search .= $Crit->getExpression() . " AND ";
			}
			$Query .= substr($search, 0, strlen($search) - 5) . ") ";
		}

		//build the search string
		if (!is_null($this->SearchControl)  && $this->SearchControl->GetQuery() != "")
		{
			$search = (stristr($Query, "where") ? " AND ( " : " WHERE (");
			$Query .= $search . $this->SearchControl->GetQuery() . " ) ";
		}
		
		// Render the columns headers, extracting sort info for our query
		$OrderBy = " ORDER BY ";
		if ($this->Columns)
		{
			foreach($this->Columns as $Column)
			{
				if ($Column->SortDirection != QDT_SORT_NONE)
				{ //$Column->Sortable && 
					// Build our OrderBy string
					$OrderBy .= $Column->DataMember . " " . $this->SortStrings[$Column->SortDirection] . ",";
					$AtLeast1Sort = TRUE;
				}
			}
		}
		
		// fix and attach the OrderBy string
		if ($AtLeast1Sort)
		{
			// clip the last comma
			$OrderBy = substr($OrderBy, 0, strlen($OrderBy) - 1);
			$Query .= $OrderBy;
		} 
		else //we'll make the default to sort by primary key, desc
		{
			if ($this->PrimaryKeyName != "")
			{
				$OrderBy .= "{$this->DBTableName}.{$this->PrimaryKeyName} DESC ";
				$Query .= $OrderBy;
			}
		}
		
		// paging is optional
		if (! is_null($this->PageSize) && $IgnorePaging == FALSE) 
		{
		$Query .= " LIMIT " . $this->PageSize . " OFFSET " . $this->GetOffset();
		}
		return $Query;
	}

	function PageCount(){
		$RecordCount = $this->RecordCount();
		$PageCount = (int)($RecordCount / $this->PageSize);
		if (($RecordCount % $this->PageSize) > 0) $PageCount += 1;
		return $PageCount;
	}
	
	function GetOffset(){
		if ($this->CurrentPage < 1) $this->CurrentPage = 1;
		if ($this->CurrentPage > $this->PageCount()) $this->CurrentPage = $this->PageCount();
		return (($this->PageSize * $this->CurrentPage) - $this->PageSize) + 1;
	}

	function getDataTableHTML()
	{
		$out = "";
		$DB = new DBConnection($this->ConnectionString);
		$Query = $DB->CreateQuery($this->GetQuery());
		
		$out .= "\n<!-- WARNING: The following table was generated by John Snooks' Excellent " . get_class($this) . " Class! -->\n";
		$out .= "\n<!-- QUERY: " . $this->GetQuery() . "  -->\n";
		$out .= "<table id=\"$this->id\" align=\"center\" width=\"100%\" cellspacing=\"0\" class=\"DataTable\">\n";

		// render the column headers
		$out .= "\t<tr class=\"ColumnHeader\">\n";
		foreach($this->Columns as $Column){
			if($Column->Visible)
			{
				$out .= $Column->GetHeaderHTML();
			}
		}
		$out .= "\t</tr>\n";
		//footer
		$out .= "</table>\n";
		$Query->Close();
		return $out;
	} // Render

	function getDataRowsHTML()
	{
		$out = "";
		// get the data 1st
//		echo $this->GetQuery();
		$selectrow = ""; // used as a css selector for selected datarows
		$DB = new DBConnection($this->ConnectionString);
		$Query = $DB->CreateQuery($this->GetQuery());
		
		if($Query->NumRows() > 0)
		{
			// Do the data
			while($row = $Query->FetchRow())
			{
				$out .= "\t<tr id=\"{$row[$this->PrimaryKeyName]}\" class=\"DataTableRow\" >\n";
				//onclick=\"{$this->ObjectName}.triggerEvent(" . DT_ROW_CLICKED . ", {$row[$this->PrimaryKeyName]});\"
				foreach($this->Columns as $Column)
				{
					if($Column->Visible)
					{
						$out .= "\t\t<td id=\"{$Column->DataMember}\" class=\"DataTableColumn\">";
						if (is_callable($Column->TemplateRenderFunc))
						{
							$html = "";
							call_user_func($Column->TemplateRenderFunc, $row, $Column, &$html);
							$out .= $html;
						}
						else
						{
							$out .= $row[$Column->DataMember] . "&nbsp;";
						}
						$out .= "</td>\n";
					}
				}//end: foreach($this->Columns as $Column)
				$out .= "\t</tr>\n";
			} //end: while($row = $Query->FetchRow()){
		} //end: if($Query->NumRows() > 0)
		else
		{
			$out .= $this->getTemplateCellHeader() . "There is no data to display." . $this->getTemplateCellFooter();
		}

		//footer
		$Query->Close();
		return $out;
	} // Render

	function GetTitle()
	{
		return $this->Title;
	}

	function GetSearchTitle()
	{
		$out = "";
		if (!is_null($this->SearchControl))
		{
			if ($this->SearchControl->Title != "")
			{
				$out = $this->SearchControl->Title;
			}
		}
		return $out;
	}
	
	function getSearchControlHTML()
	{
		$out = "";
		// Search form
		if (!is_null($this->SearchControl))
		{
			$out = $this->SearchControl->Render();
			//echo $out;
		}
		return $out;
	}

	function getTemplateCellHeader($Value = "", $attribute = "class")
	{
		if ($Value != "" )
		{
			$tag= "$attribute=\"$Value\"";
		}
		return "\t<tr>\n\t\t<td $tag colspan=\"" . $this->VisibleColumnCount() . "\">";
	}
	
	function getTemplateCellFooter()
	{
		return "\t\t</td>\n\t</tr>\n";
	}

	function Reset()
	{
		$this->Criteria = NULL;
		if(!is_null($this->PagingControl))
		{
			$this->PagingControl->CurrentPage = 1;
		}
	}

	function VisibleColumnCount()
	{
		$i = 0;
		foreach($this->Columns as $Column)
		{
			if($Column->Visible)
			{
				$i++;
			}
		}
		return $i;
	}
} //DataTable

class CriteriaField
{
	var $FieldName = "";
	var $Operator = "";
	var $Value;
	function CriteriaField($FieldName, $Operator = EQUALS, $Value = NULL)
	{
		$this->FieldName = $FieldName;
		$this->Operator = $Operator;
		$this->Value = $Value;
	}

	function getExpression()
	{
		$out = "{$this->FieldName} {$this->Operator} $this->Value";
		//$out .= $this->Quoted ? "\"{$this->Value}\"" : $this->Value;
		return $out;
	}
}

?>